import datetime
import json
import threading
import time

from .dbutils import zndb
from .influxutils import tdb
from .mqutils import mqclient
#-----------------------------
# install modbus libs
# pip3 install  pymodbus==2.5.3  --低版本
# pip3 install -i https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple paho-mqtt
#-----------------------------
from pymodbus.client.sync import ModbusSerialClient as ModbusClient

class TModbusTerDicts:
    def __init__(self):
        self.bus  = {}
        self.busUnit = {}
        self.task = {}
        self.register = {}
        self.variable = {}
class TModbusBus:
    def __init__(self , ter , info ):
        self.terminal = ter
        self.busInfo = info
        self.changedVarKeys =[]
        self.client = ModbusClient()
        self.inputThread = None
        self.actived = False
    def connect(self):
        res = False
        try:
            self.client = None
            comPort = self.busInfo['com_port']
            comBaudrate = self.busInfo['com_baudrate']
            comStopBit = self.busInfo['com_stopbits']
            comParity = self.busInfo['com_parity']
            comDataBit = self.busInfo['com_bytesize']
            client = ModbusClient(
                method="rtu" ,
                port= comPort , baudrate=comBaudrate , stopbits=comStopBit, parity= comParity, bytesize=comDataBit ,
                timeout= 2
                )
            self.client = client
            res = True
        except Exception as er:
            print(er)
        return  res
    def asyncInputRegister(self):
        self.inputThread = threading.Thread( target=self.inputRegister , daemon=True )
        self.inputThread.start()
    def inputRegister(self):
        try:
            self.lastInputTime = 0
            self.actived = True
            while self.actived:
                try:
                    self.changedVarKeys.clear()
                    time.sleep(2)
                    for task in self.busInfo['tasks'].values():
                        try:
                            lastTm = task['lastTm']
                            timeThreshold = task ['timeThreshold']
                            delayTm = task ['delayTm']
                            nowTm = time.time()
                            dltTm = nowTm - lastTm  - delayTm
                            if dltTm> timeThreshold:
                                task['lastTm'] = nowTm
                                if delayTm>0:
                                    time.sleep(delayTm)
                                    task['delayTm'] = 0
                                addr = task['regStart']
                                regNum = task['regNum']
                                unitId = task['unitId']
                                regKeyHead = task['regKeyHead']
                                if self.busInfo['busCode'] =='busLiyang03_modbusRtu':
                                    time.sleep(0.2)
                                result = self.client.read_holding_registers(addr, regNum, unit=unitId)
                                if  addr==40000:
                                    print(result)
                                if result.isError():
                                    task['delayTm'] = 1
                                    print(time.time() ,' task error.' , addr , regNum , unitId , self.busInfo['busCode']  , '--------------------')
                                else:
                                    self.busInfo['lastInputTm'] = time.time()
                                    #print('success' , task['regStart']  , unitId)
                                    i = 0
                                    dictRegValue = {}
                                    for regValue in result.registers:
                                        regAddr = addr + i
                                        regKey = regKeyHead + str(regAddr)
                                        dictRegValue[regKey] = regValue
                                        i=i+1
                                    changedVars = self.setRegValues( dictRegValue)
                                    self.decodeVariables(changedVars)
                                    self.notifyVariables(changedVars)
                                    #    print('input task...', addr , reg)


                        except Exception as erTask:
                            print(erTask , task)
                    self.saveChangedVariables()
                except Exception as erInput:
                    print(erInput)
                    break
            self.actived = False
        except Exception as eer:
            print(eer)

    def setRegValues(self , regValues):
        changedVars =[]
        try:
            nowTm = time.time()
            for regKey in regValues.keys():
                register = self.terminal.dicts.register[regKey]
                newRegVal = regValues[regKey]
                if register['tm'] == 0 or register['v1']!=newRegVal:
                    register['tm'] = nowTm
                    register['v0'] = register['v1']
                    register['v1'] = newRegVal
                    for varKey in register['vars']:
                        changedVars.append(varKey)
                        self.changedVarKeys.append(varKey)
        except Exception as err:
            print(err)
        return  changedVars

    def decodeVariables(self, changedVars):
        try:
            if len(changedVars) > 0:
                for varKey in changedVars:
                    status, varValue, varDisp , regVal , regNextVal = self.terminal.decoder.decode(varKey)
                    if status == 1 :
                        varObj = self.terminal.dicts.variable[varKey]
                        varObj['varValues'] = {
                            'd': varDisp,
                            'v': varValue ,
                            't': time.time(),
                            'r': regVal,
                            'rn':regNextVal
                        }
        except Exception as er :
            print(er)

    def notifyVariables(self, changedVars):
        try:
            if len(changedVars)>0:
                msgDict = {}
                for varKey in changedVars:
                    varObj = self.terminal.dicts.variable[varKey]
                    #print(varObj)
                    msgDict[varKey] = varObj['varValues']['v']
                msg = json.dumps(msgDict)
                mqclient.sendMsg("mq/zniot2024/var" , msg)
                msg = None

        except Exception as er:
            print(er)
    def saveChangedVariables(self):
        try:
            tdbDatas =[]
            for varKey in self.changedVarKeys:
                try:
                    varObj = self.terminal.dicts.variable[varKey]
                    tdbDatas.append(
                        self.terminal.getVarTDBData(varObj)
                    )
                except Exception as eer :
                    print(eer)
            self.changedVarKeys.clear()
            if len(tdbDatas)>0:
                postData = "\n".join(tdbDatas)
                tdb.write2TDB(postData,'')
                #print('post to tdb ', len(tdbDatas))
        except Exception as err:
            print(err)


class TDecode:
    '''
    status:
        0: decode fail
        1000: varKey is not exists
        1010: register is not exists
    '''
    def __init__(self , terminal):
        self.ter = terminal
        self.regs = terminal.dicts.register
        self.vars = terminal.dicts.variable
    def decode(self , varKey):
        status = 0
        varValue = 0
        varDisp = ""
        regVal = 0
        regNextVal = 0
        try:
            if varKey not in self.vars.keys():
                status = 1000
            else:
                varObj = self.vars[varKey]
                regKeyHead = varObj['regKeyHead']
                regKey = regKeyHead + str(varObj['regStart'])
                reg = self.regs[regKey]
                regVal = reg['v1']
                funType =  varObj['funType']
                if funType =='U32' or funType == 'I32' or funType == 'U32Time' :
                    regNextKey = regKeyHead + str(varObj['regStart']+1)
                    regNextVal = self.regs[regNextKey]['v1']
                if funType =='Bit' :
                    status , varValue , varDisp = self.decodeBit(varObj , regVal , regNextVal )
                elif funType =='I16' :
                    status , varValue , varDisp = self.decodeI16(varObj , regVal , regNextVal )
                elif funType =='U16' :
                    status , varValue , varDisp = self.decodeU16(varObj , regVal , regNextVal )
                elif funType =='I32' :
                    status , varValue , varDisp = self.decodeI32(varObj , regVal , regNextVal )
                elif funType =='U32' :
                    status , varValue , varDisp = self.decodeU32(varObj , regVal , regNextVal )
                elif funType =='U32Time' :
                    status , varValue , varDisp = self.decodeU32Time(varObj , regVal , regNextVal )
                #msg = varObj['varDesc'] +"\t" + varDisp
                #print(msg)
        except Exception as er :
            print(er)
        return status , varValue , varDisp , regVal , regNextVal
    def decodeBit(self , variable , regVal , regNextVal):
        status = 0
        varValue = 0
        varDisp = ""
        try:
            bitStart = variable['bitStart']
            mask = 1<< bitStart
            flag = regVal & mask
            if flag >0 :
                varValue = 1
                varDisp = "ON"
            else:
                varValue = 0
                varDisp = "OFF"
            status = 1
        except Exception as er :
            print('bit decode error',er)
        return status , varValue , varDisp
    def decodeU16(self , variable , regVal , regNextVal):
        status = 0
        varValue = 0
        varDisp = ""
        rate = variable['rate']
        rate1 = variable['rate1']
        decLen = variable['decLen']
        varUnit = variable['varUnit']
        try:
            varValue = regVal
            varDisp = str(varValue)
            if rate!=1 :
                varValue = varValue /( rate*1.0 )
            if rate1!=1 :
                varValue = varValue /( rate1*1.0 )
            if decLen>0 :
                varDisp = ('%.'+str(decLen)+'f') % varValue
            varDisp = varDisp +" "+ varUnit
            status = 1
        except Exception as er :
            print('u16 decode error',er)
        return status , varValue , varDisp
    def decodeI16(self , variable , regVal , regNextVal):
        status = 0
        varValue = 0
        varDisp = ""
        rate = variable['rate']
        rate1 = variable['rate1']
        decLen = variable['decLen']
        varUnit = variable['varUnit']
        try:
            if regVal> 0x7fff:
                varValue = -1 * ( (~regVal) + 1)
            else:
                varValue = regVal
            varDisp = str(varValue)
            if rate!=1 :
                varValue = varValue /( rate*1.0 )
            if rate1!=1 :
                varValue = varValue /( rate1*1.0 )
            if decLen>0 :
                varDisp = ('%.'+str(decLen)+'f') % varValue
            varDisp = varDisp +" "+ varUnit
            status = 1
        except Exception as er :
            print('i16 decode error',er)
        return status , varValue , varDisp
    def decodeU32(self , variable , regVal , regNextVal):
        status = 0
        varValue = 0
        varDisp = ""
        rate = variable['rate']
        rate1 = variable['rate1']
        decLen = variable['decLen']
        varUnit = variable['varUnit']
        try:
            varValue = (regVal << 16 ) + regNextVal
            varDisp = str(varValue)
            if rate!=1 :
                varValue = varValue /( rate*1.0 )
            if rate1!=1 :
                varValue = varValue /( rate1*1.0 )
            if decLen>0 :
                varDisp = ('%.'+str(decLen)+'f') % varValue
            varDisp = varDisp +" "+ varUnit
            status = 1
        except Exception as er :
            print('u32 decode error',er)
        return status , varValue , varDisp
    def decodeI32(self , variable , regVal , regNextVal):
        status = 0
        varValue = 0
        varDisp = ""
        rate = variable['rate']
        rate1 = variable['rate1']
        decLen = variable['decLen']
        varUnit = variable['varUnit']
        try:
            rv = (regVal<<16) + regNextVal
            if rv> 0x7fffffff:
                varValue = -1 * (  (~rv) + 1)
            else:
                varValue = rv
            varDisp = str(varValue)
            if rate!=1 :
                varValue = varValue /( rate*1.0 )
            if rate1!=1 :
                varValue = varValue /( rate1*1.0 )
            if decLen>0 :
                varDisp = ('%.'+str(decLen)+'f') % varValue
            varDisp = varDisp +" "+ varUnit
            status = 1
        except Exception as er :
            print('i32 decode error',er)
        return status , varValue , varDisp
    def decodeU32Time(self , variable , regVal , regNextVal):
        status = 0
        varValue = 0
        varDisp = ""
        try:
            varValue = (regVal << 16 ) + regNextVal
            varValue = varValue - 8 * 3600
            d= datetime.datetime.fromtimestamp(varValue)
            varDisp = d.strftime("%Y-%m-%d %H:%M:%S")
            status = 1
        except Exception as er :
            print('u32 decode error',er)
        return status , varValue , varDisp

class TModbusTer:
    def __init__(self):
        self.dicts = TModbusTerDicts()
        self.dbCode=""
        self.buses = {}
        self.decoder = TDecode(self)
    def start(self):
        print('start modbus terminal')
        try:
            for bus in self.buses.values():
                if not  bus.actived:
                    flag = bus.connect()
                    if flag :
                        bus.asyncInputRegister()
        except Exception as er:
            print(er)
    def loadDatas(self):
        print('load modbus terminal datas...')
        try:
            dbRes = zndb.queryByKey( self.dbCode, "terDatas", {})
            busList = dbRes["datasetList"][0]
            for item in busList :
                self.dicts.bus[item['busKey']] = item
                self.dicts.bus[item['busKey']]['busUnits']={}
                self.dicts.bus[item['busKey']]['tasks']={}

                bus = TModbusBus(self , item)
                self.buses[item['busKey']] = bus

            busUnitList = dbRes["datasetList"][1]
            for item in busUnitList :
                try:
                    self.dicts.busUnit[item['busUnitKey']] = item
                    self.dicts.bus[item['busKey']]['busUnits'][item['busUnitKey']] = item
                except Exception as ee:
                    print(ee)

            varList = dbRes["datasetList"][2]
            for item in varList :
                try:
                    busKey = item['busKey']
                    busUnitKey = item['busUnitKey']
                    varKey = item['varKey']
                    taskKey = item['taskKey']
                    regKeyHead = item['regKeyHead']
                    item["varValues"] = {
                        "d":"",
                        "v":0,
                        "t":0,
                        "r":0,
                        "rn":0
                    }
                    self.dicts.variable[varKey] = item

                    if taskKey not in self.dicts.task.keys():
                        task = {
                            "taskKey": taskKey ,
                            "regStart": item["regStart"],
                            "regNum": item["regLen"],
                            "regKeyHead" :item['regKeyHead'],
                            "unitId": self.dicts.busUnit[busUnitKey]['unitId'] ,
                            "lastTm":0 ,
                            "timeThreshold": 1 ,
                            "delayTm" :0
                        }
                        self.dicts.task[taskKey] = task
                        self.buses[busKey].busInfo['tasks'][taskKey] = task
                    else:
                        task = self.dicts.task[taskKey]
                        if task['regNum'] < item['regLen']:
                            task['regNum'] = item['regLen']

                    for reg in range( item['regStart'] ,item['regStart'] + item['regLen']):
                        regKey = regKeyHead+str(reg)
                        if regKey in self.dicts.register.keys():
                            self.dicts.register[regKey]['vars'].append(varKey)
                        else:
                            self.dicts.register[regKey] = {
                                "regKey": regKey ,
                                "regAddr": reg ,
                                "vars" :[varKey],
                                "v0":0 ,
                                "v1":0 ,
                                "tm":0
                            }

                except Exception as ee:
                    print(ee)

            print(self.dicts)
        except Exception as er:
            print(er)
    def getVarInfo(self , varObj):
        res = {
            'id': varObj['varId'],
            'dc':varObj['devCode'],
            'l':varObj['varDesc'],
            'v':varObj['varValues']['v'],
            'd':varObj['varValues']['d'],
            't':varObj['varValues']['t'],
            'r':varObj['varValues']['r'],
            'rn':varObj['varValues']['rn'],
            'bi':varObj['bitStart']
        }
        return  res

    def getVarTDBData(self , varObj):
        res =""
        try:
            tabname = "varZniot2024"
            tags= {
                "buseCode": varObj['busCode'] ,
                'unitId':varObj['unitId'],
                'devCode':varObj['devCode'],
                'varId':varObj['varId']
            }
            fields = [  x+'='+str(tags[x]) for x in tags.keys()]
            tm = str(int(1000*1000*1000*varObj['varValues']['t']))
            v = str(int(varObj['varValues']['v']))
            res = tabname+","+",".join(fields)+' v='+v+' '+tm
        except Exception as er:
            print(er)
        return  res

    def getVariableDebug(self , varId):
        res={
            "varId": varId,
            "regs":[]
        }
        try:
            varObj = self.dicts.variable['var$'+str(varId)]
            res['varInfo'] = varObj
            regStar = varObj['regStart']
            regLen = varObj['regLen']
            regHead = varObj['regKeyHead']
            for r in range(regStar , regStar+ regLen ):
                rk = regHead + str(r)
                regObj = self.dicts.register[rk]
                res['regs'].append(regObj)

        except Exception as er:
            print(er)
        return res
znModbusTer = TModbusTer()